#include "QUtil.h"

/**
 * @param object is the receiver (sender is the function caller, but is not the real sender)
 */
void QUtil::doInObjectThread(QObject* object, const std::function<void(void)>& func) { QMetaObject::invokeMethod(object, func, Qt::QueuedConnection); }

/**
* PreciseTimer: Accuracy=1ms
* CoarseTimer: Accuracy=5% of Duration, is default!
* VeryCoarseTimer: Accuracy=1s
*/
void QUtil::delayExecute(const std::function<void(void)>& func, int intervalMs, Qt::TimerType timerType) {
	QTimer::singleShot(intervalMs, timerType, func);
}

void QUtil::loopExecute(const std::function<void(void)>& func, int intervalMs, Qt::TimerType timerType, Qt::ConnectionType connType) {
	auto timer = new QTimer;
	timer->setInterval(intervalMs);
	timer->setSingleShot(false);
	timer->setTimerType(timerType);
	timer->start();
	QObject::connect(timer, &QTimer::timeout, timer, func, connType);
}

void QUtil::loopSeamless(const std::function<void()>& func) {
	auto sharedTimerFunc = QSharedPointer<std::function<void()>>::create();
	*sharedTimerFunc = [func, sharedTimerFunc] {
		func();
		QTimer::singleShot(0, Qt::TimerType::PreciseTimer, *sharedTimerFunc);
	};
	QTimer::singleShot(0, Qt::TimerType::PreciseTimer, *sharedTimerFunc);
}

void QUtil::loopExecuteTerminable(const std::function<bool()>& func, int intervalMs, Qt::TimerType timerType, int startUpDelayMs) {
	auto sharedTimerFunc = QSharedPointer<std::function<void()>>::create();
	*sharedTimerFunc = [func, intervalMs, timerType, sharedTimerFunc] {
		bool ret = func();
		if (!ret) return;// Ending/Terminate cycle
		QTimer::singleShot(intervalMs, timerType, *sharedTimerFunc);
	};
	QTimer::singleShot(startUpDelayMs, timerType, *sharedTimerFunc);
}

QThread* QUtil::loopInNewThread(const std::function<void(void)>& func, int intervalMs, Qt::TimerType timerType) {
	auto thread = QThread::create([func, intervalMs, timerType] {
		auto timer = new QTimer();
		timer->setTimerType(timerType);
		QObject::connect(timer, &QTimer::timeout, func);
		timer->start(intervalMs);
		QObject::connect(QThread::currentThread(), &QThread::finished, timer, &QObject::deleteLater);
		/**
		* @note QEventLoop loop; it is a stack object, and when the thread function ends (i.e. when the event loop exit), the loop of the stack object will be automatically destroyed
		*       because of EventLoop, local varyings always in their lifecycle
		*/
		QEventLoop().exec(); // startup EventLoop, codes below will not executed before it return!
	});
	thread->start();
	return thread;
}

void QUtil::destroyThread(QThread*& thread) {
	thread->quit();// stop EventLoop(if the thread does not have an event loop), exec() will return
	thread->wait();// wait for thread's safe exit
	thread->deleteLater(); // arrange the destroy in EventLoop
}

void QUtil::destroyTimer(QTimer*& timer) {
	timer->stop();
	timer->deleteLater();
}

void QUtil::printCurrentQThread(const QString& tag) {
	qInfo() << tag << "\n" << QThread::currentThread() << "\nIs main GUI thread:" << isMainThread();
}

void QUtil::handleCommandArgs(const QString& name, const QString& description, const std::function<void(void)>& func) {
	QCommandLineParser parser;
	QCommandLineOption option(name, description);// name, description
	parser.addOption(option);
	parser.process(*qApp);
	if (parser.isSet(option)) {// --name
		qInfo() << "command: " << option.description();
		func();
	}
}

/**
* @brief print log of Qt system
* @note call it before QApplication is created
* @details log categories see @link https://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html#logging-support */
void QUtil::enableQtEnvLog(const CStringList& logCategories) {
	if (logCategories.empty())
		return;
	QByteArray logRules;
	for (const auto& category : logCategories)
		logRules.append(category.c_str()).append("=true;");
	qputenv("QT_LOGGING_RULES", logRules);
}

/**
 * @brief get eth0 and wlan0's IPv4 address
 * @param out_eth0_ip [out] get eth0's IP address
 * @param out_wlan0_ip [out] get wlan0' IP address
 */
void QUtil::getLocalIP(QHostAddress* out_eth0_ip, QHostAddress* out_wlan0_ip) {
	foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) {
		// only check interfaces that are enabled and not looped
		if (!(interface.flags() & QNetworkInterface::IsUp) || (interface.flags() & QNetworkInterface::IsLoopBack))
			continue;
		// get the first IPv4 address of the interface
		foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) {
			const QHostAddress& ip = entry.ip();
			if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
				QString interfaceName = interface.name();
				if (IsEnv_Linux) {
					if (out_eth0_ip && interfaceName == "eth0" && out_eth0_ip->isNull())
						*out_eth0_ip = ip;
					else if (out_wlan0_ip && interfaceName == "wlan0" && out_wlan0_ip->isNull())
						*out_wlan0_ip = ip;
				} else if (IsEnv_PC) {
					if (out_eth0_ip && interfaceName.startsWith("ethernet_") && out_eth0_ip->isNull())
						*out_eth0_ip = ip;
					else if (out_wlan0_ip && interfaceName.startsWith("wireless_") && out_wlan0_ip->isNull())
						*out_wlan0_ip = ip;
				}
			}
		}
	}
}